/***************************************************************************
* Copyright (c) 2012-2015 VMware, Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
***************************************************************************/
package com.vmware.bdd.aop.software;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Callable;
import com.vmware.bdd.service.job.JobConstants;
import com.vmware.bdd.software.mgmt.exception.SoftwareManagementException;
import com.vmware.bdd.software.mgmt.plugin.exception.SoftwareManagementPluginException;
import com.vmware.bdd.software.mgmt.plugin.intf.PreStartServices;
import com.vmware.bdd.utils.CommonUtil;
import org.apache.log4j.Logger;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.transaction.annotation.Transactional;
import com.vmware.aurora.composition.concurrent.ExecutionResult;
import com.vmware.aurora.composition.concurrent.Priority;
import com.vmware.aurora.composition.concurrent.Scheduler;
import com.vmware.aurora.vc.VcCache;
import com.vmware.aurora.vc.VcVirtualMachine;
import com.vmware.aurora.vc.vcservice.VcContext;
import com.vmware.aurora.vc.vcservice.VcSession;
import com.vmware.bdd.entity.ClusterEntity;
import com.vmware.bdd.entity.NodeEntity;
import com.vmware.bdd.exception.BddException;
import com.vmware.bdd.manager.intf.IClusterEntityManager;
import com.vmware.bdd.service.sp.NoProgressUpdateCallback;
import com.vmware.bdd.software.mgmt.plugin.aop.PreConfiguration;
import com.vmware.bdd.software.mgmt.plugin.exception.InfrastructureException;
import com.vmware.bdd.utils.VcVmUtil;
@Aspect
public class DefaultPreStartServicesAdvice implements PreStartServices {
private static final Logger logger = Logger.getLogger(DefaultPreStartServicesAdvice.class);
private IClusterEntityManager clusterEntityMgr;
public IClusterEntityManager getClusterEntityMgr() {
return clusterEntityMgr;
}
@Autowired
public void setClusterEntityMgr(IClusterEntityManager clusterEntityMgr) {
this.clusterEntityMgr = clusterEntityMgr;
}
@Around("@annotation(com.vmware.bdd.software.mgmt.plugin.aop.PreConfiguration)")
public Object preClusterConfiguration(ProceedingJoinPoint pjp) throws Throwable {
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod();
PreConfiguration beforeConfig = AnnotationUtils.findAnnotation(method, PreConfiguration.class);
String nameParam = beforeConfig.clusterNameParam();
String[] paramNames = signature.getParameterNames();
Object[] args = pjp.getArgs();
String clusterName = null;
for (int i = 0; i < paramNames.length; i++) {
if (paramNames[i].equals(nameParam)) {
clusterName = (String)args[i];
}
}
if (clusterName == null) {
logger.error("Cluster name is not specified in method");
throw BddException.INTERNAL(null, "Wrong annotation usage. Cluster name must be specified in method.");
}
ClusterEntity cluster = clusterEntityMgr.findByName(clusterName);
if (cluster == null) {
throw BddException.NOT_FOUND("Cluster", clusterName);
}
preStartServices(clusterName);
return pjp.proceed();
}
@Override
public void preStartServices(String clusterName) throws SoftwareManagementPluginException {
preStartServices(clusterName, Collections.EMPTY_LIST, false);
}
@Override
public void preStartServices(String clusterName, List<String> addedNodeNameList, boolean force) throws SoftwareManagementPluginException {
logger.info("Pre configuration for cluster " + clusterName);
synchronized(this) {
if (clusterEntityMgr == null) {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath*:/../spring/*-context.xml");
clusterEntityMgr = (IClusterEntityManager) context.getBean("clusterEntityManager");
}
}
waitVmBootup(clusterName, force);
updateNodes(clusterName, addedNodeNameList, force);
}
private void waitVmBootup(String clusterName, boolean force) {
List<NodeEntity> nodes = clusterEntityMgr.findAllNodes(clusterName);
Callable<Void>[] callables = new Callable[nodes.size()];
int i = 0;
for (NodeEntity node : nodes) {
if (node.getMoId() == null || node.getMoId().isEmpty()) {
continue;
}
node = clusterEntityMgr.getNodeWithNicsByMobId(node.getMoId());
if (force && !node.nicsReady()) {
continue;
}
WaitVMStatusTask task = new WaitVMStatusTask(node.getMoId());
callables[i] = task;
i++;
}
try {
NoProgressUpdateCallback callback = new NoProgressUpdateCallback();
ExecutionResult[] result =
Scheduler.executeStoredProcedures(Priority.INTERACTIVE, callables,
callback);
if (result.length == 0) {
logger.error("Waiting for nodes bootup task is not executed.");
throw BddException.INTERNAL(null, "Waiting for nodes bootup task is not executed.");
}
List<String> errorMsgList = new ArrayList<String>();
for (i = 0; i < callables.length; i++) {
if (result[i].throwable != null) {
errorMsgList.add(result[i].throwable.getMessage());
logger.error(result[i].throwable.getMessage());
}
}
if (!errorMsgList.isEmpty()) {
if (force) {
logger.warn(JobConstants.FORCE_CLUSTER_OPERATION_IGNORE_EXCEPTION);
} else {
throw InfrastructureException.WAIT_VM_STATUS_FAIL(clusterName, errorMsgList);
}
}
} catch (InterruptedException e) {
String errorMessage = "error when waiting for nodes bootup";
logger.error(errorMessage);
if (force) {
logger.warn(JobConstants.FORCE_CLUSTER_OPERATION_IGNORE_EXCEPTION);
} else {
throw BddException.INTERNAL(e, e.getMessage());
}
}
}
@Transactional
private Void updateNodes(final String clusterName, final List<String> addedNodeNameList, final boolean force) {
return VcContext.inVcSessionDo(new VcSession<Void>() {
@Override
protected Void body() throws Exception {
List<NodeEntity> nodes = clusterEntityMgr.findAllNodes(clusterName);
for (NodeEntity node : nodes) {
if (node.getMoId() == null || node.getMoId().isEmpty()) {
continue;
}
node = clusterEntityMgr.getNodeWithNicsByMobId(node.getMoId());
if (force && !node.nicsReady()) {
logger.warn(String.format("try to remove node %s for nicsNotReady", node.getVmName()));
//remove the node from resizing nodes list
addedNodeNameList.remove(node.getVmName());
continue;
}
VcVirtualMachine vm = VcCache.getIgnoreMissing(node.getMoId());
String hostname = VcVmUtil.getMgtHostName(vm, node.getPrimaryMgtIpV4());
if (hostname == null || hostname.isEmpty()) {
String errMsg = "Failed to get FQDN from vm " + vm.getName();
logger.error(errMsg);
if (force) {
logger.warn(JobConstants.FORCE_CLUSTER_OPERATION_IGNORE_EXCEPTION);
logger.warn(String.format("try to remove node %s for NoFQDN", node.getVmName()));
//remove the node from resizing nodes list
addedNodeNameList.remove(node.getVmName());
continue;
} else {
throw SoftwareManagementException.FAILED_TO_GET_FQDN(vm.getName());
}
}
if (!hostname.equals(node.getGuestHostName())) {
node.setGuestHostName(hostname);
clusterEntityMgr.update(node);
logger.info("Update management NIC FQDN of node " + node.getVmName() + " to " + node.getGuestHostName());
}
}
return null;
}
});
}
}